package net.cloudcodex.server;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import net.cloudcodex.server.data.Data;
import net.cloudcodex.server.data.Data.Campaign;
import net.cloudcodex.server.data.Data.CharacterNote;
import net.cloudcodex.server.data.Data.Message;
import net.cloudcodex.server.data.Data.Notification;
import net.cloudcodex.server.data.Data.Scene;
import net.cloudcodex.server.data.Data.User;
import net.cloudcodex.server.data.campaign.character.CharacterDescriptionSDO;
import net.cloudcodex.server.data.campaign.character.CharacterSDO;
import net.cloudcodex.server.data.campaign.msg.SceneSDO;
import net.cloudcodex.server.data.campaign.scene.SceneToCreateSDO;
import net.cloudcodex.server.data.home.CampaignPublicSummary;
import net.cloudcodex.server.data.home.CampaignSummary;
import net.cloudcodex.server.data.home.CharacterSummary;
import net.cloudcodex.shared.MessageAction;
import net.cloudcodex.shared.MessageType;
import net.cloudcodex.shared.NotificationType;
import net.cloudcodex.shared.dto.NotificationDTO;
import net.cloudcodex.shared.dto.campaign.character.CharacterDescriptionDTO;
import net.cloudcodex.shared.dto.campaign.character.CharacterHeaderDTO;
import net.cloudcodex.shared.dto.campaign.character.CharacterNoteDTO;
import net.cloudcodex.shared.dto.home.CampaignPublicSummaryDTO;
import net.cloudcodex.shared.dto.home.CampaignSummaryDTO;
import net.cloudcodex.shared.dto.home.CharacterSummaryDTO;
import net.cloudcodex.shared.dto.msg.MessageDTO;
import net.cloudcodex.shared.dto.msg.SceneDTO;
import net.cloudcodex.shared.dto.msg.SceneToCreateDTO;

import com.google.appengine.api.datastore.Key;

/**
 * Utility class to map data to DTO.
 * @author Thomas
 */
public class DTOUtil {
	
	private DTOUtil() { }
	
	/**
	 * Create a {@link CharacterSummaryDTO} from {@link CharacterSummary}.
	 * @param summary character summary to map.
	 * @return character summary DTO.
	 */
	public static CharacterSummaryDTO mapCharacterSummary(CharacterSummary summary) {

		if(summary == null) {
			return null;
		}

		final Data.Character character = summary.getCharacter();
		
		final CharacterSummaryDTO dto = new CharacterSummaryDTO();
		dto.setId(character.getKey().getId());
		dto.setCampaign(mapCampaignPublicSummary(summary.getCampaign()));
		dto.setName(character.getName());
		dto.setDescription(character.getDescription());
		dto.setIcon(character.getIcon());
		dto.setNotifications(mapNotification(
				summary.getNotifications(), NotificationType.CHARACTER));
		dto.setCreationDate(character.getDate());
		dto.setDead(character.getDead() == null ? false : character.getDead());
		dto.setLocked(character.getLocked() == null ? false : character.getLocked());
		
		return dto;
	}

	/**
	 * Map a list of characters summary.
	 */
	public static List<CharacterSummaryDTO> mapCharacterSummary(
			Collection<? extends CharacterSummary> summaries) {
		
		if(summaries == null || summaries.isEmpty()) {
			return null;
		}
		
		final List<CharacterSummaryDTO> dtos = new ArrayList<CharacterSummaryDTO>();
		for(CharacterSummary summary : summaries) {
			dtos.add(mapCharacterSummary(summary));
		}
		
		return dtos.isEmpty() ? null : dtos;
	}

	/**
	 * Map a Campaign to {@link Campaignpublic staticSummaryDTO}.
	 * @param campaign campaign to map.
	 * @return campaign summary DTO.
	 */
	public static CampaignPublicSummaryDTO mapCampaignPublicSummary(CampaignPublicSummary summary) {
		
		if(summary == null) {
			return null;
		}
		
		final Campaign campaign = summary.getCampaign();
		final User master = summary.getMaster();
		final CampaignPublicSummaryDTO dto = new CampaignPublicSummaryDTO();
		dto.setId(campaign.getKey().getId());
		dto.setName(campaign.getName());
		dto.setIcon(campaign.getIcon());
		dto.setNotifications(mapNotification(
				summary.getNotifications(), NotificationType.CAMPAIGN));
		dto.setCreationDate(campaign.getDate());
		dto.setGameMasterNickname(master == null ? null : master.getNickname());
		return dto;
	}

	/**
	 * Map a Campaign to {@link CampaignSummaryDTO}.
	 * @param campaign campaign to map.
	 * @return campaign summary DTO.
	 */
	public static CampaignSummaryDTO mapCampaignSummary(CampaignSummary summary) {
		
		if(summary == null) {
			return null;
		}
		
		final Campaign campaign = summary.getCampaign();
		final User master = summary.getMaster();
		final CampaignSummaryDTO dto = new CampaignSummaryDTO();
		dto.setId(campaign.getKey().getId());
		dto.setName(campaign.getName());
		dto.setGame(campaign.getGame());
		dto.setIcon(campaign.getIcon());
		dto.setNotifications(mapNotification(
				summary.getNotifications(), NotificationType.CAMPAIGN));
		dto.setCreationDate(campaign.getDate());
		dto.setGameMasterNickname(master == null ? null : master.getNickname());
		dto.setGame(campaign.getGame());
		
		// iterate to keep only the names.
		final List<Data.Character> characters = summary.getCharacters();
		if(characters != null) {
			final List<String> names = new ArrayList<String>();
			for(Data.Character character : characters) {
				names.add(character.getName());
			}
			dto.setCharacters(names.isEmpty() ? null : names);
		}
		
		return dto;
	}

	/**
	 * map a list of campaign summaries.
	 */
	public static List<CampaignSummaryDTO> mapCampaignSummary(
			Collection<? extends CampaignSummary> summaries) {

		if(summaries == null || summaries.isEmpty()) {
			return null;
		}
		
		final List<CampaignSummaryDTO> dtos = new ArrayList<CampaignSummaryDTO>();
		for(CampaignSummary summary : summaries) {
			dtos.add(mapCampaignSummary(summary));
		}
		
		return dtos.isEmpty() ? null : dtos;
	}

	/**
	 * Map a notification to its DTO.
	 * @param notification notification to map.
	 * @param type type of the notification.
	 * @return the notification DTO.
	 */
	public static NotificationDTO mapNotification(Notification notification, NotificationType type) {
		
		if(notification == null || type == null) {
			return null;
		}
		
		final NotificationDTO dto = new NotificationDTO();
		dto.setId(notification.getKey().getId()); // FIXME 
		dto.setDate(notification.getTimestamp());
		dto.setMessage(notification.getMessage());
		dto.setType(type);
		
		return dto;
	}

	/**
	 * Map a list of notifications to DTO.
	 */
	public static List<NotificationDTO> mapNotification(
			Collection<? extends Notification> notifications, NotificationType type) {
		
		if(notifications == null || type == null) {
			return null;
		}
		
		final List<NotificationDTO> dtos = new ArrayList<NotificationDTO>();
		for(Notification notification : notifications) {
			final NotificationDTO dto = mapNotification(notification, type);
			if(dto != null) {
				dtos.add(dto);
			}
		}
		
		return dtos.isEmpty() ? null : dtos;
	}

	/**
	 * Map a CharacterSDO to CharacterHeaderDTO.
	 * @param sdo character to map.
	 * @return CharacterHeaderDTO.
	 */
	public static CharacterHeaderDTO mapCharacterSDOToHeader(CharacterSDO sdo, Long viewCharacterKey) {
		
		if(sdo == null) {
			return null;
		}
		
		return mapCharacterToHeader(sdo.getCharacter(), null, viewCharacterKey);
	}

	/**
	 * Map a Data.Character to CharacterHeaderDTO.
	 * @param sdo character to map.
	 * @param forCharacter the character who sees the character.
	 * @return CharacterHeaderDTO.
	 */
	public static CharacterHeaderDTO mapCharacterToHeader(
			Data.Character character, Scene scene, 
			Long viewCharacterKey) {
		
		final CharacterHeaderDTO dto = new CharacterHeaderDTO();
		dto.setId(character.getKey().getId());
		dto.setIcon(character.getIcon());
		dto.setDescription(character.getDescription());
		dto.setProfile(Boolean.TRUE.equals(character.getProfile()));
		
		final Key ownerKey = character.getOwner();

		// character can see that a character header is itself an kwow the owner
		if(viewCharacterKey == null || viewCharacterKey.equals(dto.getId())) {
			dto.setOwner(ownerKey == null ? null : ownerKey.getId());
			dto.setOwnerNickname(character.getOwnerNickname());
			dto.setDead(character.getDead());
			dto.setLocked(character.getLocked());
		}
		
		if(viewCharacterKey == null) {
			dto.setName(character.getName());

			if(scene != null) {
				// transform aliases, but only the character aliases.
				final Map<String, String> allAliases = scene.getAlias();
				if(allAliases != null) {
					final Map<Long, String> aliases = dto.getAliases();
					final String stringId = String.valueOf(dto.getId());
					final String prefix = stringId + "-";
					
					for(Map.Entry<String, String> entry : allAliases.entrySet()) {
						final String key = entry.getKey();
						if(key != null) {
							if(stringId.equals(key)) {
								aliases.put(null, entry.getValue());
							} else if(key.startsWith(prefix)) {
								try {
									final Long id = Long.valueOf(key.substring(prefix.length()));
									aliases.put(id, entry.getValue());
								} catch (NumberFormatException e) {
									// nothing ...
								}
							}
						}
					}
				}
			}
			
		} else {
			// try to get an alias
			final String alias = getCharacterAlias(dto.getId(), scene, viewCharacterKey);
			
			if(alias != null) {
				dto.setName(alias);
			} else {
				dto.setName(character.getName());
			}
		}
		
		return dto;
	}
	
	/**
	 * Utility method, try to get an alias of the character for the optional
	 * character in the scene.
	 */
	public static String getCharacterAlias(
		long character, Scene scene, Long viewCharacterKey) {
		
		if(scene == null) {
			return null;
		}
		
		String alias = null;

		if(viewCharacterKey != null) {
			// try to get a specific alias
			alias = scene.getAlias(character + "-" + viewCharacterKey);
		}

		// try to get global alias
		return alias != null ? alias : scene.getAlias(String.valueOf(character));
	}
	

	/**
	 * Map a list of CharacterSDO to a list of CharacterHeaderDTO.
	 */
	public static List<CharacterHeaderDTO> mapCharacterSDOToHeader(
			Collection<? extends CharacterSDO> sdos, Long viewCharacterKey) {
		
		if(sdos == null) {
			return null;
		}
		
		final List<CharacterHeaderDTO> dtos = new ArrayList<CharacterHeaderDTO>();
		for(CharacterSDO sdo : sdos) {
			final CharacterHeaderDTO dto = mapCharacterSDOToHeader(sdo, viewCharacterKey);
			if(dto != null) {
				dtos.add(dto);
			}
		}
		
		return dtos.isEmpty() ? null : dtos;
	}
	
	/**
	 * Map a list of CharacterSDO to a list of CharacterHeaderDTO.
	 */
	public static List<CharacterHeaderDTO> mapCharacterHeader(
			Collection<? extends Data.Character> characters, 
			Scene scene, Long viewCharacterKey) {
		
		if(characters == null) {
			return null;
		}
		
		final List<CharacterHeaderDTO> dtos = new ArrayList<CharacterHeaderDTO>();
		for(Data.Character character : characters) {
			final CharacterHeaderDTO dto = mapCharacterToHeader(character, scene, viewCharacterKey);
			if(dto != null) {
				dtos.add(dto);
			}
		}
		
		return dtos.isEmpty() ? null : dtos;
	}

	/**
	 * Map a CharacterDescriptionSDO to a CharacterDescriptionDTO.
	 * @param sdo SDO to map.
	 * @return the DTO version.
	 */
	public static CharacterDescriptionDTO mapCharacterDescription(CharacterDescriptionSDO sdo) {

		if(sdo == null) {
			return null;
		}
		
		final CharacterDescriptionDTO dto = new CharacterDescriptionDTO();
		dto.setNotes(mapCharacterNote(sdo.getNotes()));
		dto.setCampaignId(sdo.getCampaignId());
		dto.setId(sdo.getCharacterId());
		dto.setTimestamp(sdo.getTimestamp());
		dto.setAliases(sdo.getAliases());
		dto.setSheet(sdo.getSheet());

		return dto;
	}

	/**
	 * Map a {@link CharacterNote} to {@link CharacterNoteDTO}.
	 * @param note note to map.
	 * @return DTO version.
	 */
	public static CharacterNoteDTO mapCharacterNote(Data.CharacterNote note) {
		
		if(note == null) {
			return null;
		}
		
		final CharacterNoteDTO dto = new CharacterNoteDTO();
		final Key authorKey = note.getAuthor();
		dto.setAuthor(authorKey == null ? null : authorKey.getId());
		dto.setContent(note.getContent());
		dto.setTimestamp(note.getTimestamp());
		
		return dto;
	}
	
	/**
	 * Map a list of {@link CharacterNote} to a list of {@link CharacterNoteDTO}.
	 */
	public static List<CharacterNoteDTO> mapCharacterNote(
			Collection<? extends Data.CharacterNote> sdos) {
		
		if(sdos == null) {
			return null;
		}
		
		final List<CharacterNoteDTO> dtos = new ArrayList<CharacterNoteDTO>();
		for(CharacterNote sdo : sdos) {
			final CharacterNoteDTO dto = mapCharacterNote(sdo);
			if(dto != null) {
				dtos.add(dto);
			}
		}
		
		return dtos.isEmpty() ? null : dtos;
	}

	/**
	 * @return Map a Scene to a SceneDTO.
	 * @param scene scene to map.
	 * @param viewCharacterKey the character id for which return the data. 
	 * 		If null then it's for GM.
	 */
	public static SceneDTO mapScene(
		SceneSDO sdo, 
		Long viewCharacterKey
	) {
		if(sdo == null) {
			return null;
		}
		
		final Scene scene = sdo.getScene();
		final List<Message> messages = sdo.getMessages();
		final List<Data.Character> characters = sdo.getCharacters();
		
		final SceneDTO dto = new SceneDTO();
		dto.setId(scene.getKey().getId());
		dto.setCharacters(mapCharacterHeader(characters, scene, viewCharacterKey));
		dto.setMessages(mapMessage(messages));
		
		// use introduction as message
		final String introduction = scene.getIntroduction();
		if(introduction != null) {
			if(dto.getMessages() == null) {
				dto.setMessages(new ArrayList<MessageDTO>());
			}
			final MessageDTO messageDTO = new MessageDTO();
			messageDTO.setId(null);
			messageDTO.setAction(null);
			messageDTO.setAuthor(null);
			messageDTO.setContent(introduction);
			messageDTO.setIndex(0);
			messageDTO.setType(null);
			dto.getMessages().add(0, messageDTO);
		}
		dto.setTimestamp(scene.getTimestamp());
		
		if(viewCharacterKey != null) {
			// for a character, only its own informations
			final String viewCharacterKeyString = viewCharacterKey.toString();
			final Key next = scene.getNext(viewCharacterKeyString);
			if (next != null) {
				dto.getNext().put(viewCharacterKey, next.getId());
			}
			final Key previous = scene.getPrevious(viewCharacterKeyString);
			if (previous != null) {
				dto.getPrevious().put(viewCharacterKey, previous.getId());
			}
		} else {
			// for GM : all informations
			for(Map.Entry<String, Key> entry : scene.getNext().entrySet()) {
				try {
					final Long id = Long.valueOf(entry.getKey());
					dto.getNext().put(id, entry.getValue().getId());
				} catch (NumberFormatException e) {
					// ...
				}
			}
			for(Map.Entry<String, Key> entry : scene.getPrevious().entrySet()) {
				try {
					final Long id = Long.valueOf(entry.getKey());
					dto.getPrevious().put(id, entry.getValue().getId());
				} catch (NumberFormatException e) {
					// ...
				}
			}
		}
		
		return dto;
	}

	/**
	 * Map a list of {@link SceneSDO} to a list of {@link SceneDTO}.
	 */
	public static List<SceneDTO> mapScene(
			Collection<? extends SceneSDO> scenes,
			Long viewCharacterKey
	) {
		if(scenes == null) {
			return null;
		}
		
		final List<SceneDTO> dtos = new ArrayList<SceneDTO>();
		for(SceneSDO scene : scenes) {
			final SceneDTO dto = mapScene(scene, viewCharacterKey);
			if(dto != null) {
				dtos.add(dto);
			}
		}
		
		return dtos.isEmpty() ? null : dtos;
	}
	
	/**
	 * Map a {@link SceneSDO} to a {@link MessageDTO}.
	 */
	public static MessageDTO mapMessage(Message message) {
		
		final MessageDTO dto = new MessageDTO();
		dto.setId(message.getKey().getId());
		dto.setType(MessageType.getMessageType(message.getType()));
		dto.setAction(MessageAction.getMessageAction(message.getAction()));
		dto.setIndex(message.getIndex() == null ? 0 : message.getIndex());
		if(Boolean.TRUE.equals(message.getDeleted())) {
			dto.setDeleted(true);
		} else {
			dto.setContent(message.getContent());
			dto.setDices(message.getDices());
		}

		final Key author = message.getAuthor();
		dto.setAuthor(author == null ? null : author.getId());
		
		return dto;
	}
	
	/**
	 * Map a list of {@link SceneSDO} to a list of {@link MessageDTO}.
	 */
	public static List<MessageDTO> mapMessage(
			Collection<? extends Data.Message> messages) {
	
		if(messages == null) {
			return null;
		}
		
		final List<MessageDTO> dtos = new ArrayList<MessageDTO>();
		for(Message message : messages) {
			final MessageDTO dto = mapMessage(message);
			if(dto != null) {
				dtos.add(dto);
			}
		}
		
		return dtos.isEmpty() ? null : dtos;
	}

	/**
	 * Create a {@link SceneToCreateSDO} from {@link SceneToCreateDTO}.
	 */
	public static SceneToCreateSDO mapSceneToCreate(SceneToCreateDTO dto) {

		if(dto == null) {
			return null;
		}

		final SceneToCreateSDO sceneToCreate = new SceneToCreateSDO();
		sceneToCreate.setCharacters(dto.getCharacters());
		sceneToCreate.setIntroduction(dto.getIntroduction());
		sceneToCreate.getAliases().putAll(dto.getAliases());
		
		return sceneToCreate;
	}
	
}
